﻿using LexicalAnalyser.DesignArchitecture;
using LexicalAnalyser.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LexicalAnalyser.Implementation
{
    public class Lexer : ILexer
    {
        public string _dataStream;
        public char _c;
        public int _index;
        public StringBuilder _currentWord;
        public List<TokenElement> _listelements;
        public int _lineNo;

        public List<TokenElement> Execute_LexicalAnalyser(string code)
        {
            InitializeAll(code);
            Execute();
            return _listelements;
        }

        public void InitializeAll(string InputStream)
        {
            _dataStream = InputStream;
            _index = 0;
            _lineNo = 0;
            _currentWord = new StringBuilder();
            _listelements = new List<TokenElement>();
        }

        public char getNextChar()
        {
            if (_index >= _dataStream.Length)
                return '\0';
            return _dataStream[_index++];
        }

        public List<TokenElement> getListElemets()
        {
            return _listelements;
        }

        public bool IsSpecial(char c)
        {
            string special = "{}[]()!~@#$%^&*(<=>)+-;_";
            return special.Contains(c);
        }

        public void Execute()
        {
            FiniteStates currentstate = FiniteStates.InitialState;
            while ((_c = getNextChar()) != '\0')
            {

                #region SPECIAL CHARACTERS HANDLER

                if (IsSpecial(_c))
                {
                    switch (currentstate)
                    {
                        case FiniteStates.InitialState:
                            break;
                        case FiniteStates.Numbers:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Constants:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Words:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Comments:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.COMMENTS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Undefined:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.ToString()));
                            _currentWord.Clear();
                            //retaking last input
                            _index--;
                            break;
                    }
                    _listelements.Add(new TokenElement(_lineNo, Tokens.SPECIAL_SYMBOLS, _c.ToString()));
                    currentstate = FiniteStates.InitialState;
                    continue;
                }

                #endregion

                #region SYMBOL HANDLER

                if (char.IsSymbol(_c))
                {
                    switch (currentstate)
                    {
                        case FiniteStates.InitialState:
                            break;
                        case FiniteStates.Numbers:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Constants:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Words:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Comments:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.COMMENTS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Undefined:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.ToString()));
                            _currentWord.Clear();
                            //retaking last input
                            _index--;
                            break;
                    }
                    _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _c.ToString()));
                    currentstate = FiniteStates.InitialState;
                    continue;
                }

                #endregion

                #region NEW LINE HANDLER

                // in case of next line
                if (_c == '\n')
                {
                    switch (currentstate)
                    {
                        case FiniteStates.InitialState: break;
                        case FiniteStates.Numbers:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Constants:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Words:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Comments:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.COMMENTS, _currentWord.ToString()));
                            _currentWord.Clear();
                            break;
                        case FiniteStates.Undefined:
                            _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.ToString()));
                            _currentWord.Clear();
                            //retaking last input
                            _index--;
                            break;
                    }
                    _lineNo++;
                    currentstate = FiniteStates.InitialState;
                    continue;
                }

                #endregion

                #region INITIAL FINITE STATE

                // When Current State equals Initial State
                if (currentstate == FiniteStates.InitialState)
                {
                    if (char.IsNumber(_c))
                    {
                        currentstate = FiniteStates.Numbers;
                        _currentWord.Append(_c);
                        continue;
                    }
                    if (char.IsLetter(_c))
                    {
                        currentstate = FiniteStates.Words;
                        _currentWord.Append(_c);
                        continue;
                    }
                    if (_c == ' ')
                        continue;
                }

                #endregion

                #region UNDEFINED FINITE STATE

                if (currentstate == FiniteStates.Undefined)
                {
                    _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.ToString()));
                    _currentWord.Clear();
                    currentstate = FiniteStates.InitialState;

                    _index--;       // retaking the last input ...
                    continue;
                }

                #endregion

                #region NUMBERS FINITE STATE


                // when current state equals Numbers
                if (currentstate == FiniteStates.Numbers)
                {
                    if (_c == '.')
                    {
                        if (_currentWord.ToString().Contains('.'))
                        {
                            _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.Append('.').ToString()));
                            _currentWord.Clear();
                            currentstate = FiniteStates.InitialState;
                            continue;
                        }

                        _c = getNextChar();
                        if (char.IsNumber(_c))
                        {
                            _currentWord.Append("." + _c);
                            continue;
                        }
                    }

                    if (char.IsNumber(_c))
                    {
                        _currentWord.Append(_c);
                        continue;
                    }

                    if (_c == ' ')
                    {
                        _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                        _currentWord.Clear();
                        currentstate = FiniteStates.InitialState;
                        continue;
                    }

                    if (!char.IsNumber(_c))
                    {
                        _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                        _currentWord.Clear();
                        _currentWord.Append(_c);
                        currentstate = FiniteStates.Undefined;
                        continue;
                    }
                }


                #endregion

                #region ALPHABETS FINITE STATE

                //when current state is Alphabets
                if (currentstate == FiniteStates.Words)
                {
                    if (char.IsLetterOrDigit(_c))
                    {
                        _currentWord.Append(_c);
                        continue;
                    }

                    if (_c == ' ')
                    {
                        _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                        _currentWord.Clear();
                        currentstate = FiniteStates.InitialState;
                        continue;
                    }

                    if (!char.IsLetterOrDigit(_c))
                    {
                        _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                        _currentWord.Clear();
                        _currentWord.Append(_c);
                        currentstate = FiniteStates.Undefined;
                        continue;
                    }

                }

                #endregion


            }
            if (_currentWord.ToString().Count() > 0)
            {
                switch (currentstate)
                {
                    case FiniteStates.Words:
                        _listelements.Add(new TokenElement(_lineNo, Tokens.KEYWORDS, _currentWord.ToString()));
                        break;

                    case FiniteStates.Numbers:
                        _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                        break;

                    case FiniteStates.Constants:
                        _listelements.Add(new TokenElement(_lineNo, Tokens.LITERALS, _currentWord.ToString()));
                        break;

                    case FiniteStates.Comments:
                        _listelements.Add(new TokenElement(_lineNo, Tokens.COMMENTS, _currentWord.ToString()));
                        break;

                    case FiniteStates.InitialState:
                    case FiniteStates.Undefined:
                        _listelements.Add(new TokenElement(_lineNo, Tokens.UNIDENTIFIED, _currentWord.ToString()));
                        break;
                }
            }
        }
    }
}
